package com.by.auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Ye QS
 * @version 0.1
 * @className TokenTemplate
 * @description
 * @date 2024/9/6 15:16
 * @since jdk11
 */
@Component
public class TokenTemplate {
    // 通过@Value注解从配置文件中读取token的秘密密钥属性值
    @Value("${token.secret}")
    private String secret;

    // 通过@Value注解从配置文件中读取token的有效时间（毫秒单位）属性值，默认为30分钟
    @Value("${token.validTime}")
    private long validTime;

    // 定义一个默认有效期常量，如果未从配置文件中获得有效时间，则使用该默认值（30分钟）
    private final long DEFAULT_TIME = 30 * 60 * 1000;

    /**
     * 生成一个新的JWT令牌。
     *
     * @param payload 载荷数据，通常包含需要存储在令牌中的信息（如用户ID、角色等）
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(Map payload) {
        // 创建一个新的JWT令牌构建器，并设置头部信息
        String token = JWT.create().withHeader(getHeader())
                // 设置载荷payload，这里包含了要嵌入到JWT中的具体信息
                .withPayload(payload)
                // 设置过期时间点为当前时间加上有效时间长度
                .withExpiresAt(getValidDate())
                // 使用HMAC256算法签名，并指定密钥secret
                .sign(Algorithm.HMAC256(secret));

        return token;
    }

    /**
     * 验证给定的JWT令牌是否有效，并返回验证结果。
     *
     * @param token 待验证的JWT令牌值
     * @return 包含验证状态码、消息和解析后的载荷对象的ValidResult实例
     */
    public ValidResult validResult(String token) {
        // 检查传入的token是否为空或仅由空白字符组成
        if (null == token || "".equals(token)) {
            return new ValidResult(0, "token为空", null);
        }

        DecodedJWT verify = null;

        try {
            // 构建JWT验证器，使用HMAC256算法并指定密钥secret
            verify = JWT.require(Algorithm.HMAC256(secret)).build()
                    // 验证给定的token是否有效，并返回解码后的JWT对象
                    .verify(token);
        } catch (TokenExpiredException e) {
            // 如果捕获到TokenExpiredException异常，则表明token已过期
            return new ValidResult(0, "token已过期", null);
        } catch (Exception e) {
            // 对于其他所有类型的异常，认为该token无效
            return new ValidResult(1, "token无效", null);
        }

        // 获取已验证JWT中所有的claims
        Map<String, Claim> claims = verify.getClaims();

        // 返回包含验证状态码、消息以及转换后的载荷对象的ValidResult实例
        return new ValidResult(2, "验证成功", convert(claims));
    }

    /**
     * 刷新或重新生成JWT令牌。
     *
     * @param token 待刷新的JWT令牌值
     * @return 新生成的JWT令牌字符串；如果原token无效则返回null
     */
    public String refreshToken(String token) {
        // 首先对传入的token进行有效性验证，并获取验证结果
        ValidResult result = validResult(token);

        if (result.getCode() == 1) { // token无效
            // 如果验证结果显示token无效，则直接返回null表示无法刷新
            return null;
        } else if (result.getCode() == 0) { // 已经过期
            // 如果验证结果显示token已过期，则解码该token以提取原始载荷信息
            DecodedJWT decodedJWT = JWT.decode(token);

            // 获取用户信息（即解析出的所有claim）
            Map<String, Claim> map = decodedJWT.getClaims();

            // 使用原始载荷信息生成新的token
            return generateToken(convert(map));
        }

        // 若token正常，直接从验证结果中提取用户的载荷信息
        Map<String, String> map = result.getUser();

        // 根据现有的载荷信息生成新的token并返回
        return generateToken(map);
    }

    /**
     * 将Claim集合转换成String键值对形式的Map。
     *
     * @param claimMap 原始的Claim映射表
     * @return 字符串键值对形式的新Map实例
     */
    private Map<String, String> convert(Map<String, Claim> claimMap) {
        // 创建一个新的HashMap实例用于存放字符串键值对
        HashMap<String, String> tmp = new HashMap<>();

        // 遍历输入的Claim映射表，并将每个Claim对象转换为字符串形式后存入新的Map中
        claimMap.forEach((k, v) -> {
            tmp.put(k, v.asString());
        });

        // 返回填充好的Map实例
        return tmp;
    }

    /**
     * 返回一个表示未来一段时间之后的有效日期。
     *
     * @return 代表从现在起validTime后的时间点的Date实例
     */
    private Date getValidDate() {
        // 计算当前时间戳加上有效时间长度（毫秒单位）后的值
        long time = System.currentTimeMillis() + getValidTime();

        // 根据计算出的时间创建新的Date实例并返回
        return new Date(time);
    }

    /**
     * 返回包含类型和算法标识符的Map，通常用于配置JWT头部信息。
     *
     * @return 包含typ和alg键值对的HashMap实例
     */
    Map getHeader() {
        // 创建一个新的HashMap实例
        Map map = new HashMap<>();

        // 向map中添加键值对"typ": "JWT"，表示这是一个JWT类型的令牌
        map.put("typ", "JWT");

        // 向map中添加键值对"alg": "HS256"，表示使用的签名算法是HMAC-SHA256
        map.put("alg", "HS256");

        // 返回填充好的Map实例
        return map;
    }

    /**
     * 如果secret没有被初始化，则随机生成一个新的秘密密钥。
     *
     * @return 当前使用的秘密密钥
     */
    public String getSecret() {
        if (null == secret) {
            // 如果secret尚未设置，则生成一个新的UUID作为默认密钥
            secret = UUID.randomUUID().toString();
        }

        // 返回当前使用的秘密密钥
        return secret;
    }

    /**
     * 设置新的秘密密钥。
     *
     * @param secret 密钥字符串
     */
    public void setSecret(String secret) {
        this.secret = secret;
    }

    /**
     * 获取当前有效的token存活时间长度。
     *
     * @return 指定的有效时间（毫秒单位），如果没有指定则采用默认值
     */
    public long getValidTime() {
        if (validTime == 0) {
            // 如果未显式设定有效期，则使用预定义的DEFAULT_TIME常量值
            validTime = DEFAULT_TIME;
        }

        // 返回当前设定的有效时间长度
        return validTime;
    }

    /**
     * 设置新的有效时间长度。
     *
     * @param validTime 有效时间（毫秒单位）
     */
    public void setValidTime(long validTime) {
        this.validTime = validTime;
    }
}
